/*
 * 代号：凤凰
 * 2014年6月30日
 * V3.0
 */
package com.jphenix.servlet.filter;

import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.share.lang.SString;
import com.jphenix.share.tools.FileCopyTools;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.standard.docs.BeanInfo;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.servlet.IBytesFilter;
import com.jphenix.standard.servlet.IFilter;
import com.jphenix.standard.servlet.IRequest;
import com.jphenix.standard.servlet.IResponse;

import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 参数信息过滤器 过滤 <%=key%> 
 * 
 * 2019-06-15 按照IFilter增加了过滤器初始化方法
 * 2019-08-19 优化了流，增加了缓存
 *  
 * @author 马宝刚
 * 2014年6月30日
 */
@ClassInfo({"2019-08-19 18:16","参数信息过滤器"})
@BeanInfo({"parameterfilter"})
public class ParameterFilter extends ABase implements IFilter,IBytesFilter {

	public final static int BUFFER_SIZE = 128;      //缓存大小
    private FilterConfig       config       = null; //过滤器配置信息处理类
    private Map<String,String> parameterMap = null; //参数信息对照容器
    
    /**
     * 构造函数
     * @author 马宝刚
     */
    public ParameterFilter() {
        super();
    }

    /**
     * 覆盖函数
     */
    @Override
    public int getIndex() {
        return 5;
    }
    
	/**
	 * 执行初始化
	 * @param fe         过滤器管理类
	 * @param config     Servlet配置信息类
	 * @throws Exception 异常（如果初始化发生异常，则放弃不再使用）
	 * 2019年6月15日
	 * @author MBG
	 */
	@Override
	public void init(FilterExplorer fe, FilterConfig config) throws Exception {
		this.config = config;
	}
    
    /**
     * 设置参数对照容器
     * @param paraMap 参数对照容器
     * 2014年6月30日
     * @author 马宝刚
     */
    public void setParameterMap(Map<String,String> paraMap) {
        this.parameterMap = paraMap;
    }
    
    /**
     * 获取参数对照容器
     * @return 参数对照容器
     * 2014年6月30日
     * @author 马宝刚
     */
    public Map<String,String> getParameterMap(){
        if(parameterMap==null) {
            parameterMap = new HashMap<String,String>();
        }
        return parameterMap;
    }
    
    
    /**
     * 覆盖函数
     */
    @Override
    public boolean doFilter(IRequest req, IResponse resp) throws Exception {
        //动作路径
        String uri  = req.getServletPath();
        //文件路径
        String filename = SFilesUtil.getAllFilePath(uri,req.getWebBasePath());
        File file = new File(filename); //构建要解析的文件
        if (file.exists()) {
            if (!file.isDirectory()) {
                serveFile(req, resp, canonicalizePath(uri), file);
            } else {
                log.log("ParameterFilter Error->Not found the file:["+filename+"]");
                resp.sendError(HttpServletResponse.SC_NOT_FOUND,req);
            }
        } else {
            log.log("ParameterFilter Error->Not found the file:["+filename+"]");
            resp.sendError(HttpServletResponse.SC_NOT_FOUND,req);
        }
        /*
         * 标记该动作已经被处理完
         * 因为操作过读入流，该过滤器读取完之后，
         * 就不能被其他过滤器读取了。
         */
        return true;
    }
    
    /**
     * 处理目标文件相对路径
     * 刘虻
     * 2012-7-27 上午9:01:40
     * @param path 页面请求中的文件路径信息
     * @return 处理后的文件相对路径
     */
    protected String canonicalizePath(String path) {
        if (path == null || path.length() == 0) {
            return path;
        }
        List<String> pathElems = new ArrayList<String>(6);
        char[] pa = path.toCharArray();
        int n = pa.length;
        int s = -1;
        int lev = 0;
        for (int i = 0; i < n; i++) {
            if (s < 0) {
                if (pa[i] != '/' && pa[i] != '\\') {
                    s = i;
                }
            } else {
                boolean f = false;
                if (pa[i] == '?') {
                    f = true;
                }
                if (pa[i] == '/' || pa[i] == '\\' || f) {
                    String el = new String(pa, s, i - s);
                    if ("..".equals(el)) {
                        if (pathElems.size() > 0) {
                            pathElems.remove(pathElems.size() - 1);
                        } else {
                            lev--;
                        }
                        // else exception ?
                    } else if (".".equals(el) == false) {
                        if (lev >= 0) {
                            pathElems.add(el);
                        } else {
                            lev++;
                        }
                    }
                    if (f) {
                        s = i;
                        break;
                    }
                    s = -1;
                }
            }
        }
        if (s > 0) {
            String el = new String(pa, s, n - s);
            if ("..".equals(el)) {
                if (pathElems.size() > 0) {
                    pathElems.remove(pathElems.size() - 1);
                }
                // else exception ?
            } else if (".".equals(el) == false) {
                if (lev >= 0) {
                    pathElems.add(el);
                }
            }
        } else {
            pathElems.add("");
        }
        if (pathElems.size() == 0) {
            return "";
        }
        StringBuffer result = new StringBuffer(n);
        result.append(pathElems.get(0));
        n = pathElems.size();
        for (int i = 1; i < n; i++) {
            result.append('/').append(pathElems.get(i));
        }
        // System.err.println("Before "+path+" after "+result);
        return result.toString();
    }
    
    /**
     * 处理文件内容
     * 刘虻
     * 2013-4-6 下午10:03:25
     * @param file 指定文件
     * @param req 页面请求
     * @return 处理后的文件内容字节数组
     * @throws Exception 异常
     */
    protected byte[] fixContent(File file, HttpServletRequest req) throws Exception {
        //构建返回值
        ByteArrayOutputStream reBos = new ByteArrayOutputStream();
        FileInputStream in = new FileInputStream(file); //文件读入流
        /*
         * 开头标识
         * 0没有读到关键字
         * 1读到关键字的第一个字节 <
         * 2读到关键字的第二个字节 <%= 中的%
         * 3读到关键字的第三个字节 <%= 中的 =  准备读取主键
         * 4读取到语言代码结束点： 准备开始读取语言内容
         * 5读取到语言信息段结束标识的第一个字节 *
         */
        int bStep = 0;
        try {
            int                   rCount;                         //读取字节数
            byte[]                buffer = new byte[BUFFER_SIZE]; //缓存
            ByteArrayOutputStream keyBos = null;                  //读取到的语言编码
            while((rCount=in.read(buffer))!=-1) {
            	for(int i=0;i<rCount;i++) {
                    if(bStep==0) {
                        //获取处理信息节点关键字的第一个字节
                        if(buffer[i]=='<') {
                            bStep=1;
                        }else {
                            reBos.write(buffer[i]);
                        }
                    }else if(bStep==1) {
                        //获取处理信息节点关键字的第二个字节
                        if(buffer[i]=='%') {
                            bStep = 2;
                        }else {
                            //头一个{不是关键字
                            bStep = 0;
                            reBos.write('<');
                            reBos.write(buffer[i]);
                        }
                    }else if(bStep==2) {
                        //获取处理信息节点关键字的第三个字节，准备读取参数主键
                        if(buffer[i]=='=') {
                            bStep = 3;
                            keyBos = new ByteArrayOutputStream();
                        }else {
                            //头一个{不是关键字
                            bStep = 0;
                            reBos.write('<');
                            reBos.write('%');
                            reBos.write(buffer[i]);
                        }
                    }else if(bStep==3) {
                        //读取参数主键
                        if(buffer[i]=='%') {
                            //结束符主键的第一个字符
                            bStep = 4;
                        }else {
                            keyBos.write(buffer[i]);
                        }
                    }else if(bStep==4) {
                        //读取参数结束关键字
                        if(buffer[i]=='>') {
                            //结束符主键的最后一个
                            bStep = 0;
                            String key = keyBos.toString();
                            if("ctx".equals(key.toLowerCase())) {
                                reBos.write(req.getContextPath().getBytes());
                            }else {
                                reBos.write(SString.valueOf(getParameterMap().get(keyBos.toString())).getBytes());
                            }
                            keyBos = null;
                        }else {
                            //不是结束标识，继续读取参数主键
                            bStep = 3;
                            keyBos.write('%');
                            keyBos.write(buffer[i]);
                        }
                    }
                
            	}
            }
        }finally {
            try {
                in.close();
            }catch(Exception e2) {}
        }
        return reBos.toByteArray();
    }
    
    /**
     * 输出指定文件内容到页面中
     * 刘虻
     * 2012-7-27 上午9:09:36
     * @param req                       页面请求
     * @param res                       页面反馈
     * @param path                  文件路径
     * @param file                      文件对象
     * @throws IOException      异常
     */
    protected void serveFile(
             IRequest req
            ,IResponse res
            ,String path
            ,File file) throws IOException {
        if (!file.canRead()) {
            res.sendError(HttpServletResponse.SC_FORBIDDEN,req);
            return;
        } else
            // by Niel Markwick
        {
            try {
                file.getCanonicalPath();
            } catch (Exception e) {
                res.sendError(HttpServletResponse.SC_FORBIDDEN,
                        "Forbidden, exception:" + e,req);
                return;
            }
        }
        // Handle If-Modified-Since.
        res.setStatus(HttpServletResponse.SC_OK);
        //设置内容类型
        res.setContentType(config.getServletContext().getMimeType(file.getName()));
        //获取处理后的文件内容
        byte[] contentBytes;
        try {
            contentBytes= fixContent(file,req); 
        }catch(Exception e) {
            e.printStackTrace();
            res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,req);
            return;
        }
        if (contentBytes.length < Integer.MAX_VALUE) {
            res.setContentLength(contentBytes.length);
        }else {
            res.setHeader("Content-Length", Long.toString(contentBytes.length));
        }
        res.setDateHeader("Last-modified", file.lastModified());
        try {
            //输出内容
            res.getOutputStream().write(contentBytes);
        }catch(Exception e) {}
    }

    /**
     * 执行过滤
     * @param bytes 过滤前的字节数组
     * @param req 页面请求对象 
     * @param resp 页面反馈对象
     * @return 过滤后的字节数组
     * 2014年6月30日
     * @author 马宝刚
     */
    @Override
    public byte[] doBytesFilter(byte[] bytes,IRequest req,IResponse resp) {
        //构建返回值
        ByteArrayOutputStream reBos = new ByteArrayOutputStream();
        /*
         * 开头标识
         * 0没有读到关键字
         * 1读到关键字的第一个字节 <
         * 2读到关键字的第二个字节 <%= 中的%
         * 3读到关键字的第三个字节 <%= 中的 =  准备读取主键
         * 4读取到语言代码结束点： 准备开始读取语言内容
         * 5读取到语言信息段结束标识的第一个字节 *
         */
        int bStep = 0;
        try {
            ByteArrayOutputStream keyBos = null; //读取到的语言编码
            for(byte ele:bytes) {
                if(bStep==0) {
                    //获取处理信息节点关键字的第一个字节
                    if(ele=='<') {
                        bStep=1;
                    }else {
                        reBos.write(ele);
                    }
                }else if(bStep==1) {
                    //获取处理信息节点关键字的第二个字节
                    if(ele=='%') {
                        bStep = 2;
                    }else {
                        //头一个{不是关键字
                        bStep = 0;
                        reBos.write('<');
                        reBos.write(ele);
                    }
                }else if(bStep==2) {
                    //获取处理信息节点关键字的第三个字节，准备读取参数主键
                    if(ele=='=') {
                        bStep = 3;
                        keyBos = new ByteArrayOutputStream();
                    }else {
                        //头一个{不是关键字
                        bStep = 0;
                        reBos.write('<');
                        reBos.write('%');
                        reBos.write(ele);
                    }
                }else if(bStep==3) {
                    //读取参数主键
                    if(ele=='%') {
                        //结束符主键的第一个字符
                        bStep = 4;
                    }else {
                        keyBos.write(ele);
                    }
                }else if(bStep==4) {
                    //读取参数结束关键字
                    if(ele=='>') {
                        //结束符主键的最后一个
                        bStep = 0;
                        String key = keyBos.toString();
                        if("ctx".equals(key.toLowerCase())) {
                            reBos.write(req.getContextPath().getBytes());
                        }else {
                            reBos.write(SString.valueOf(getParameterMap().get(keyBos.toString())).getBytes());
                        }
                        keyBos = null;
                    }else {
                        //不是结束标识，继续读取参数主键
                        bStep = 3;
                        keyBos.write('%');
                        keyBos.write(ele);
                    }
                }
            }
        }catch(Exception e) {
            e.printStackTrace();
        }
        return reBos.toByteArray();
    }
    
    /**
     * 获取需要过滤的动作路径扩展名
     * 
     * 多个扩展名用逗号分割
     * 多个扩展名用半角逗号分割
     * 扩展名前不用加半角聚号（.)
     * 如果返回空，或者空字符串，说明需要过滤全部动作路径（不建议这么做）
     * 如果需要过滤无扩展名的动作，用半角减号（-）标记
     * 
     * @return
     * 2014年9月12日
     * @author 马宝刚
     */
    @Override
    public String getFilterActionExtName() {
        return "html,js,css";
    }

    /**
     * 读取指定文档内容并做内容处理
     * @param filePath     文档全路径
     * @param req          页面请求
     * @param resp         页面反馈
     * @return             处理后的文件内容
     * @throws Exception   异常
     * 2017年9月11日
     * @author MBG
     */
	@Override
	public byte[] doFileFilter(String filePath, IRequest req, IResponse resp) throws Exception {
    	//目标文件
    	File file = new File(filePath);
    	if(!file.exists()) {
    		return null;
    	}
        return doBytesFilter(FileCopyTools.copyToByteArray(file),req,resp);
	
	}
}
